﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimMath;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo ("MyTestProject")]


namespace M3G {
    public class Mesh : Node {
        #region Field
        int                submeshCount;
        VertexBuffer       vertexBuffer;
        IndexBuffer[]      indexBuffers;
        Appearance[]       appearances;
        ShaderAppearance[] shaderAppearances;
        int                morphTargetCount;
        VertexBuffer[]     morphTargets;
        float[]            morphWeights;
        int                morphSubsetCount;
        int[]              morphSubsetIndices;
        #endregion

        #region Method

        /**
         * コンストラクター
         * */
        public Mesh (int numSubmeshes, int numMorphTargets) {
            if (numSubmeshes < 1) {
                throw new ArgumentException ("Number of submesh must be within [1,inf], num=" + numSubmeshes);
            }
            if (numMorphTargets < 0) {
                throw new ArgumentException ("Number of morph target must be within [0,inf], num=" + numMorphTargets);
            }

            this.submeshCount = numSubmeshes;

            this.vertexBuffer = null;
            this.indexBuffers = new IndexBuffer[numSubmeshes];
            this.appearances = new Appearance[numSubmeshes];
            this.shaderAppearances = new ShaderAppearance[numSubmeshes];

            this.morphTargetCount = numMorphTargets;
            this.morphTargets = new VertexBuffer[numMorphTargets];
            this.morphWeights = new float[numMorphTargets];

            this.morphSubsetCount = 0;
            this.morphSubsetIndices = new int[0];
        }

        /**
         * コピーコンストラクター
         * */
        protected Mesh (Mesh mesh)
            : base (mesh) {
            this.submeshCount = mesh.submeshCount;
            this.vertexBuffer = mesh.vertexBuffer;
            this.indexBuffers = (IndexBuffer[])mesh.indexBuffers.Clone ();
            this.appearances = (Appearance[])mesh.appearances.Clone ();
            this.shaderAppearances = (ShaderAppearance[])mesh.ShaderAppearance.Clone ();
            this.morphTargetCount = mesh.morphTargetCount;
            this.morphTargets = (VertexBuffer[])mesh.morphTargets.Clone ();
            this.morphWeights = (float[])mesh.morphWeights.Clone ();
            this.morphSubsetCount = mesh.morphSubsetCount;
            this.morphSubsetIndices = (int[])mesh.morphSubsetIndices.Clone ();
        }


        public int SubmeshCount {
            get { return submeshCount; }
        }

        public VertexBuffer VertexBuffer {
            get { return vertexBuffer; }
        }

        public void SetVertexBuffer (VertexBuffer vertexBuffer) {
            this.vertexBuffer = vertexBuffer;
        }


        public IndexBuffer[] IndexBuffer {
            get {
                return (IndexBuffer[])indexBuffers.Clone ();
            }
        }

        public void SetIndexBuffer (int index, IndexBuffer submesh) {
            if (index < 0 || index > submeshCount) {
                throw new ArgumentException ("Index must be within [0," + (submeshCount - 1) + "], index=" + index);
            }

            this.indexBuffers[index] = submesh;
        }

        public Appearance[] Appearance {
            get {
                return (Appearance[])appearances.Clone ();
            }
        }

        public void SetAppearance (int index, Appearance appearance) {
            if (index < 0 || index >= submeshCount) {
                throw new ArgumentException ("Index must be within [0," + (submeshCount - 1) + "], index=" + index);
            }

            this.appearances[index] = appearance;
        }

        public ShaderAppearance[] ShaderAppearance {
            get { return (ShaderAppearance[])shaderAppearances.Clone (); }
        }

        public void SetShaderAppearance (int index, ShaderAppearance appearance) {
            if (index < 0 || index >= submeshCount) {
                throw new ArgumentException ("Index must be within [0," + (submeshCount - 1) + "], index=" + index);
            }

            this.shaderAppearances[index] = appearance;
        }

        public int GetMorphSubset (int[] morphIndices) {
            if (morphIndices != null && morphIndices.Length < morphSubsetCount) {
                throw new ArgumentException ("Length of morphIndices is less than required, len=" + morphIndices.Length + ",req=" + morphSubsetCount);
            }

            Array.Copy (morphSubsetIndices, morphIndices, morphSubsetCount);

            return morphSubsetCount;
        }

        public void SetMorphSubset (int morphSubsetSize, int[] morphSubsetIndices) {
            if (morphSubsetIndices == null) {
                throw new ArgumentNullException ("MorphSubsetIndices is null");
            }
            if (morphSubsetSize < 0 || morphSubsetSize > 65535) {
                throw new ArgumentException ("Morph subset size must be within [0,65535], size=" + morphSubsetSize);
            }
            if (morphSubsetIndices.Length < morphSubsetSize) {
                throw new ArgumentOutOfRangeException ("Length of morphIndices is less than required, len=" + morphSubsetIndices.Length + "<" + morphSubsetSize);
            }
            for (int i=0; i < morphSubsetSize; i++) {
                if (morphSubsetIndices[i] < 0 || morphSubsetIndices[i] > 65535) {
                    throw new ArgumentOutOfRangeException ("Indices must be within [0,65535], index=" + morphSubsetIndices[i]);
                }
                if (i > 1 && morphSubsetIndices[i - 1] > morphSubsetIndices[i]) {
                    throw new ArgumentException ("Indices must be increasing order, index[" + (i - 1) + "]>index[" + i + "]");
                }
            }

            this.morphSubsetCount = morphSubsetSize;
            this.morphSubsetIndices = new int[morphSubsetSize];
            Array.Copy (morphSubsetIndices, this.morphSubsetIndices, morphSubsetSize);

        }

        public int MorphTargetCount {
            get { return morphTargetCount; }
        }

        public VertexBuffer[] MorphTarget {
            get {
                return (VertexBuffer[])morphTargets.Clone ();
            }
        }

        public void SetMorphTarget (int index, VertexBuffer target) {
            if (index < 0 || index > morphTargetCount) {
                throw new ArgumentOutOfRangeException ("Index must be within [0," + (morphTargetCount - 1) + "], index=" + index);
            }

            this.morphTargets[index] = target;
        }

        // ここMorphWeightsだろ常識的に考えて
        public float[] Weights {
            get {
                return (float[])morphWeights.Clone ();
            }
        }

        public void SetWeights (float[] weights) {
            if (weights == null) {
                throw new ArgumentNullException ("Weights is null");
            }
            if (weights.Length < morphTargetCount) {
                throw new ArgumentException ("Length of weights is less than required, len=" + weights.Length + "<" + morphTargetCount);
            }

            Array.Copy (weights, this.morphWeights, morphTargetCount);
        }

        public override Object3D[] References {
            get {
                var q = new List<Object3D> ();
                q.AddRange (base.References);
                q.Add (vertexBuffer);
                q.AddRange (indexBuffers);
                q.AddRange (morphTargets);
                q.AddRange (appearances);
                q.AddRange (shaderAppearances);
                q.RemoveAll (x => x == null);
                return q.ToArray ();
            }
        }

        public override void GenerateCollisionShape (int orientations, Boolean useExisting) {
            if (useExisting && GetCollisionShape (null, null) > 0) {
                return;
            }

            // MeshのPositionからCollisionShapeを作成する
            // SkinnedMesh, MorphingMeshの変形は考慮しなくて良い
        }


        public override Object3D Duplicate () {
            var msh = new Mesh (this);
            msh.parent = null;
            return msh;
        }

        internal bool RayTriangleIntersection (float[] p1, float[] p2, float[] p3, float[] pos, float[] dir, RayIntersection ri) {
            var ray = new Ray (new Vector3 (pos), new Vector3 (dir));
            var v1  = new Vector3 (p1);
            var v2  = new Vector3 (p2);
            var v3  = new Vector3 (p3);
            float t;
            bool intersected;

            intersected = ray.Intersects (ref v1, ref v2, ref v3, out t);
            if (intersected) {
                ri.Distance = t;
                ri.Intersected = this;
                Array.Copy (pos, 0, ri.Ray, 0, 3);
                Array.Copy (dir, 0, ri.Ray, 3, 3);
                // 法線とテクスチャー座標は未実装
            }

            return intersected;
        }

        override internal bool Intersect (uint scope, float[] pos, float[] dir, RayIntersection ri) {
            if ((scope & Scope) == 0) {
                return false;
            }

            // バウンディングボリュームの判定
            var culled = CulledByBoundingVolume (pos, dir);
            if (culled) {
                return false;
            }

            var intersected = false;
            var minDistance = float.MaxValue;

            // 子ノードの探索
            // (=SkinneMeshの時はボーン階層の探索)
            // ここでやらずにSkinnedMeshで再定義しようか?
            intersected = base.Intersect (scope, pos, dir, ri);
            if (intersected) {
                minDistance = ri.Distance;
            }

            // メッシュの判定
            if (vertexBuffer == null || vertexBuffer.Positions (null) == null) {
                return intersected;
            }
            var scaleBias = new float[4];
            var positions = vertexBuffer.Positions (scaleBias);
            var scale     = scaleBias[0];
            var bias      = new[] { scaleBias[1], scaleBias[2], scaleBias[3] };

            var p1 = new float[3];
            var p2 = new float[3];
            var p3 = new float[3];

            for (var msh=0; msh < submeshCount; msh++) {
                var ibuf = indexBuffers[msh];
                if (ibuf != null && ibuf.PrimitiveType == PrimitiveType.Triangles) {
                    var primitiveCount = ibuf.IndexCount / 3;
                    var indices        = ibuf.GetIndices ();

                    for (var p=0; p < primitiveCount; p++) {
                        positions.Get (indices[p * 3 + 0], 1, p1, scale, bias);
                        positions.Get (indices[p * 3 + 1], 1, p2, scale, bias);
                        positions.Get (indices[p * 3 + 2], 1, p3, scale, bias);
                        var result = new RayIntersection ();
                        var hit    = false;
                        hit = RayTriangleIntersection (p1, p2, p3, pos, dir, result);
                        if (hit && result.Distance < minDistance) {
                            result.CopyTo (ri);
                            minDistance = result.Distance;
                            intersected = true;
                        }
                    }
                }

            }

            return intersected;
        }
        #endregion



    }
}
